﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;

namespace ca2simulator.shared
{
    public class CA2Factory
    {
        public static bool BuildCA2(CA2Raw rawData, out CA2 ca)
        {
            ResetError();
            // Modeled after: http://www.codestrider.com/BlogRead.aspx?b=e42b4dbe-146b-4a36-a39b-6c2a20ffc7a3
            ca = null;
            CSharpCodeProvider compiler = new CSharpCodeProvider();
            string[] rules = new string[1];
            rules[0] = rawData.TransferCode;
            CompilerParameters cp = new CompilerParameters();
            cp.GenerateInMemory = true;
            cp.GenerateExecutable = false;
            //Add all assemblies referenced by ITransferFunc
            Type type = typeof(ITransferFunc);
            Assembly ass = type.Assembly;
            cp.ReferencedAssemblies.Add(ass.Location);
            /*
            foreach(AssemblyName name in typeof(ITransferFunc).Assembly.GetReferencedAssemblies()){
                cp.ReferencedAssemblies.Add(name.CodeBase);
            }*/
            
            CompilerResults results = compiler.CompileAssemblyFromSource(cp, rules);
            if (results.Errors.Count != 0)
            {
                foreach (CompilerError err in results.Errors)
                {
                    RecordError(String.Format("Code Compilation Error:  {2}",err.Column, err.Line, err.ErrorText));
                }
                return false;
            }
            // Create the transfer funct
            ITransferFunc tran = (ITransferFunc)FindInterface(results.CompiledAssembly, "ITransferFunc");
            ca = new CA2(tran, rawData.InitialLattice);
            return true;
        }

        public static bool HasError{get;private set;}
        public static string ERRNO { get; private set; }

        public static bool BuildCA2Raw(string simulationFile, out CA2Raw raw)
        {
            raw = null;
            ResetError();

            using (StreamReader sr = new StreamReader(simulationFile))
            {
                string line=sr.ReadLine();
                // Read in the code
                string code = "";
                byte defaultState=0, stateCount;
                int width = 800, height = 800;
                while (!"#region TransferFunc".Equals(line))
                {
                    if (line == null)
                    {
                        RecordError("BuildCA2Raw Error: Could not find \"#region TransferFunc\".");
                        return false;
                    }
                    line = sr.ReadLine();
                }
                line = sr.ReadLine();
                while (!"#endregion".Equals(line))
                {
                    if (line == null)
                    {
                        RecordError("BuildCA2Raw Error: Could not find \"#endregion\" for region TransferFunc.");
                        return false;
                    }
                    code = String.Format("{0}\n{1}", code, line);
                    line = sr.ReadLine();
                }
                // Find lattice properties
                while (!"#region LatticeDefinition".Equals(line))
                {
                    if (line == null)
                    {
                        RecordError("BuildCA2Raw Error: Could not find \"#region LatticeDefinition\". Ensure it comes after TransferFunc.");
                        return false;
                    }
                    line = sr.ReadLine();
                }
                line = sr.ReadLine();
                while (!"#endregion".Equals(line))
                {
                    if (line == null)
                    {
                        RecordError("BuildCA2Raw Error: Could not find \"#endregion\" for region LatticeDefinition.");
                        return false;
                    }
                    if (line.Contains("defaultState=")) defaultState = Byte.Parse(line.Substring(line.IndexOf("=")+1));
                    else if (line.Contains("stateCount=")) stateCount = Byte.Parse(line.Substring(line.IndexOf("=") + 1));
                    else if (line.Contains("width=")) width = int.Parse(line.Substring(line.IndexOf("=") + 1));
                    else if (line.Contains("height=")) height = int.Parse(line.Substring(line.IndexOf("=") + 1));
                    line = sr.ReadLine();
                }
                Cell cell; cell.CellState.State = defaultState;
                Lattice initLattice = new Lattice(cell.CellState, height, width);

                // Lattice initialization
                while (!"#region LatticeInit".Equals(line))
                {
                    if (line == null)
                    {
                        RecordError("BuildCA2Raw Error: Could not find \"#region LatticeInit\". Ensure it comes after LatticeDefinition.");
                        return false;
                    }
                    line = sr.ReadLine();
                }
                line = sr.ReadLine();
                int r=0, c=0;
                byte state=defaultState;
                string trimmed;
                while (!"#endregion".Equals(line))
                {
                    if (line == null)
                    {
                        RecordError("BuildCA2Raw Error: Could not find \"#endregion\" for region LatticeInit.");
                        return false;
                    }
                    if (line.Contains("cell=("))
                    {
                        // Get values from file
                        trimmed = line.Substring("cell=(".Length);
                        r = int.Parse(trimmed.Substring(0,trimmed.IndexOf(",")));
                        trimmed = trimmed.Substring(trimmed.IndexOf(",")+1);
                        c = int.Parse(trimmed.Substring(0, trimmed.IndexOf(",")));
                        trimmed = trimmed.Substring(trimmed.IndexOf(",")+1);
                        state = byte.Parse(trimmed.Substring(0, trimmed.IndexOf(")")));

                        // Set the cell, use bounds checking just in case
                        cell.CellState.State = state;
                        initLattice[r, c, true] = cell;
                    }
                    line = sr.ReadLine();
                }

                //Build CA2Raw
                raw = new CA2Raw(initLattice, code);
                return true;
            }
            
        }
        public static bool BuildCA2(string simulationFile, out CA2 ca)
        {
            ca = null;
            CA2Raw raw;
            if (BuildCA2Raw(simulationFile, out raw))
                if (BuildCA2(raw, out ca))
                    return true;
            return false;
        }

        private static object FindInterface(Assembly anAssembly, string interfaceName)
        {
            foreach (Type aType in anAssembly.GetTypes())
            {
                if (aType.GetInterface(interfaceName, true) != null)
                    return anAssembly.CreateInstance(aType.FullName);
            }
            return null;
        }

        private static void RecordError(string message)
        {
            HasError = true;
            ERRNO = ERRNO + message + "\n";
        }
        private static void ResetError()
        {
            HasError = false;
            ERRNO = "";
        }
    }
}
